// ========================================================================
// Copyright (c) 2007-2009 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at 
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses. 
// ========================================================================

package org.eclipse.jetty.util.resource;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.StringTokenizer;

import org.eclipse.jetty.util.URIUtil;

/**
 * A collection of resources (dirs). Allows webapps to have multiple (static) sources. The first resource in the collection is the main resource. If a resource is not found in the main resource, it looks it up in the order the resources were constructed.
 */
public class ResourceCollection extends Resource
{

	private Resource[] _resources;

	/* ------------------------------------------------------------ */
	/**
	 * Instantiates an empty resource collection. This constructor is used when configuring jetty-maven-plugin.
	 */
	public ResourceCollection()
	{
		_resources = new Resource[0];
	}

	/* ------------------------------------------------------------ */
	/**
	 * Instantiates a new resource collection.
	 * 
	 * @param resources the resources to be added to collection
	 */
	public ResourceCollection(Resource... resources)
	{
		List<Resource> list = new ArrayList<Resource>();
		for (Resource r: resources)
		{
			if (r == null)
				continue;
			if (r instanceof ResourceCollection)
			{
				for (Resource r2: ((ResourceCollection)r).getResources())
					list.add(r2);
			}
			else
				list.add(r);
		}
		_resources = list.toArray(new Resource[list.size()]);
		for (Resource r: _resources)
		{
			if (!r.exists() || !r.isDirectory())
				throw new IllegalArgumentException(r + " is not an existing directory.");
		}
	}

	/* ------------------------------------------------------------ */
	/**
	 * Instantiates a new resource collection.
	 * 
	 * @param resources the resource strings to be added to collection
	 */
	public ResourceCollection(String[] resources)
	{
		_resources = new Resource[resources.length];
		try
		{
			for (int i = 0; i < resources.length; i++)
			{
				_resources[i] = Resource.newResource(resources[i]);
				if (!_resources[i].exists() || !_resources[i].isDirectory())
					throw new IllegalArgumentException(_resources[i] + " is not an existing directory.");
			}
		} catch (IllegalArgumentException e)
		{
			throw e;
		} catch (Exception e)
		{
			throw new RuntimeException(e);
		}
	}

	/* ------------------------------------------------------------ */
	/**
	 * Instantiates a new resource collection.
	 * 
	 * @param csvResources the string containing comma-separated resource strings
	 */
	public ResourceCollection(String csvResources)
	{
		setResourcesAsCSV(csvResources);
	}

	/* ------------------------------------------------------------ */
	/**
	 * Retrieves the resource collection's resources.
	 * 
	 * @return the resource array
	 */
	public Resource[] getResources()
	{
		return _resources;
	}

	/* ------------------------------------------------------------ */
	/**
	 * Sets the resource collection's resources.
	 * 
	 * @param resources the new resource array
	 */
	public void setResources(Resource[] resources)
	{
		_resources = resources != null ? resources : new Resource[0];
	}

	/* ------------------------------------------------------------ */
	/**
	 * Sets the resources as string of comma-separated values. This method should be used when configuring jetty-maven-plugin.
	 * 
	 * @param csvResources the comma-separated string containing one or more resource strings.
	 */
	public void setResourcesAsCSV(String csvResources)
	{
		StringTokenizer tokenizer = new StringTokenizer(csvResources, ",;");
		int len = tokenizer.countTokens();
		if (len == 0)
		{
			throw new IllegalArgumentException("ResourceCollection@setResourcesAsCSV(String) " +
				" argument must be a string containing one or more comma-separated resource strings.");
		}

		_resources = new Resource[len];
		try
		{
			for (int i = 0; tokenizer.hasMoreTokens(); i++)
			{
				_resources[i] = Resource.newResource(tokenizer.nextToken().trim());
				if (!_resources[i].exists() || !_resources[i].isDirectory())
					throw new IllegalArgumentException(_resources[i] + " is not an existing directory.");
			}
		} catch (Exception e)
		{
			throw new RuntimeException(e);
		}
	}

	/* ------------------------------------------------------------ */
	/**
	 * @param path The path segment to add
	 * @return The contained resource (found first) in the collection of resources
	 */
	@SuppressWarnings("null")
	@Override
	public Resource addPath(String path) throws IOException, MalformedURLException
	{
		if (_resources == null)
			throw new IllegalStateException("*resources* not set.");

		if (path == null)
			throw new MalformedURLException();

		if (path.length() == 0 || URIUtil.SLASH.equals(path))
			return this;

		Resource resource = null;
		ArrayList<Resource> resources = null;
		int i = 0;
		for (; i < _resources.length; i++)
		{
			resource = _resources[i].addPath(path);
			if (resource.exists())
			{
				if (resource.isDirectory())
					break;
				return resource;
			}
		}

		for (i++; i < _resources.length; i++)
		{
			Resource r = _resources[i].addPath(path);
			if (r.exists() && r.isDirectory())
			{
				if (resource != null)
				{
					resources = new ArrayList<Resource>();
					resources.add(resource);
					resource = null;
				}
				resources.add(r);
			}
		}

		if (resource != null)
			return resource;
		if (resources != null)
			return new ResourceCollection(resources.toArray(new Resource[resources.size()]));
		return null;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @param path
	 * @return the resource(file) if found, returns a list of resource dirs if its a dir, else null.
	 * @throws IOException
	 * @throws MalformedURLException
	 */
	@SuppressWarnings("null")
	protected Object findResource(String path) throws IOException, MalformedURLException
	{
		Resource resource = null;
		ArrayList<Resource> resources = null;
		int i = 0;
		for (; i < _resources.length; i++)
		{
			resource = _resources[i].addPath(path);
			if (resource.exists())
			{
				if (resource.isDirectory())
					break;

				return resource;
			}
		}

		for (i++; i < _resources.length; i++)
		{
			Resource r = _resources[i].addPath(path);
			if (r.exists() && r.isDirectory())
			{
				if (resource != null)
				{
					resources = new ArrayList<Resource>();
					resources.add(resource);
				}
				resources.add(r);
			}
		}

		if (resource != null)
			return resource;
		if (resources != null)
			return resources;
		return null;
	}

	/* ------------------------------------------------------------ */
	@Override
	public boolean delete() throws SecurityException
	{
		throw new UnsupportedOperationException();
	}

	/* ------------------------------------------------------------ */
	@Override
	public boolean exists()
	{
		if (_resources == null)
			throw new IllegalStateException("*resources* not set.");

		return true;
	}

	/* ------------------------------------------------------------ */
	@Override
	public File getFile() throws IOException
	{
		if (_resources == null)
			throw new IllegalStateException("*resources* not set.");

		for (Resource r: _resources)
		{
			File f = r.getFile();
			if (f != null)
				return f;
		}
		return null;
	}

	/* ------------------------------------------------------------ */
	@Override
	public InputStream getInputStream() throws IOException
	{
		if (_resources == null)
			throw new IllegalStateException("*resources* not set.");

		for (Resource r: _resources)
		{
			InputStream is = r.getInputStream();
			if (is != null)
				return is;
		}
		return null;
	}

	/* ------------------------------------------------------------ */
	@Override
	public String getName()
	{
		if (_resources == null)
			throw new IllegalStateException("*resources* not set.");

		for (Resource r: _resources)
		{
			String name = r.getName();
			if (name != null)
				return name;
		}
		return null;
	}

	/* ------------------------------------------------------------ */
	@Override
	public OutputStream getOutputStream() throws IOException, SecurityException
	{
		if (_resources == null)
			throw new IllegalStateException("*resources* not set.");

		for (Resource r: _resources)
		{
			OutputStream os = r.getOutputStream();
			if (os != null)
				return os;
		}
		return null;
	}

	/* ------------------------------------------------------------ */
	@Override
	public URL getURL()
	{
		if (_resources == null)
			throw new IllegalStateException("*resources* not set.");

		for (Resource r: _resources)
		{
			URL url = r.getURL();
			if (url != null)
				return url;
		}
		return null;
	}

	/* ------------------------------------------------------------ */
	@Override
	public boolean isDirectory()
	{
		if (_resources == null)
			throw new IllegalStateException("*resources* not set.");

		return true;
	}

	/* ------------------------------------------------------------ */
	@Override
	public long lastModified()
	{
		if (_resources == null)
			throw new IllegalStateException("*resources* not set.");

		for (Resource r: _resources)
		{
			long lm = r.lastModified();
			if (lm != -1)
				return lm;
		}
		return -1;
	}

	/* ------------------------------------------------------------ */
	@Override
	public long length()
	{
		return -1;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @return The list of resource names(merged) contained in the collection of resources.
	 */
	@Override
	public String[] list()
	{
		if (_resources == null)
			throw new IllegalStateException("*resources* not set.");

		HashSet<String> set = new HashSet<String>();
		for (Resource r: _resources)
		{
			for (String s: r.list())
				set.add(s);
		}
		String[] result = set.toArray(new String[set.size()]);
		Arrays.sort(result);
		return result;
	}

	/* ------------------------------------------------------------ */
	@Override
	public void release()
	{
		if (_resources == null)
			throw new IllegalStateException("*resources* not set.");

		for (Resource r: _resources)
			r.release();
	}

	/* ------------------------------------------------------------ */
	@Override
	public boolean renameTo(Resource dest) throws SecurityException
	{
		throw new UnsupportedOperationException();
	}

	/* ------------------------------------------------------------ */
	@Override
	public void copyTo(File destination)
		throws IOException
	{
		for (int r = _resources.length; r-- > 0;)
			_resources[r].copyTo(destination);
	}

	/* ------------------------------------------------------------ */
	/**
	 * @return the list of resources separated by a path separator
	 */
	@Override
	public String toString()
	{
		if (_resources == null)
			return "[]";

		return String.valueOf(Arrays.asList(_resources));
	}

	/* ------------------------------------------------------------ */
	@Override
	public boolean isContainedIn(Resource r) throws MalformedURLException
	{
		// TODO could look at implementing the semantic of is this collection a subset of the Resource r?
		return false;
	}

}
